Ejemplo de análisis con pandas, geopandas y folium#

Se presenta un ejemplo de análisis reproducible de datos geoespaciales: la distribución de especies de murciélagos en áreas silvestres protegidas (ASP) de Costa Rica.

Las salidas se presentan en tres formatos:

  • Tabular

  • Gráfica

  • Geoespacial

El ćodigo fuente de este ejercicio está disponible en pf3311-cienciadatosgeoespaciales/2021-iii.

Las salidas puede ser visualizadas en https://nbviewer.org/github/pf3311-cienciadatosgeoespaciales/2021-iii/blob/main/contenido/b/ejemplo-analisis-pandas-geopandas-folium.ipynb.

Para más información sobre investigación reproducible en datos geoespaciales, se recomienda ver FOSS4G2021 - Open source for open spatial data science.

Entradas#

Registros de presencia de murciélagos#

Provienen de una consulta al portal de la Infraestructura Mundial de Información en Biodiversidad (GBIF).

Para efectos de este análisis, están disponibles en https://raw.githubusercontent.com/pf3311-cienciadatosgeoespaciales/2021-iii/main/contenido/b/datos/murcielagos.csv.

Polígonos de áreas silvestres protegidas (ASP)#

Fueron publicados por el Sistema Nacional de Áreas de Conservación (Sinac) a través del Sistema Nacional de Información Territorial (SNIT), en http://geos1pne.sirefor.go.cr/wfs?.

Para efectos de este análisis, están disponibles en pf3311-cienciadatosgeoespaciales/2021-iii.

Procesamiento#

import math

import pandas as pd

import matplotlib.pyplot as plt
%matplotlib inline

import plotly.express as px

import geopandas as gpd

import folium
from folium import Marker
from folium.plugins import MarkerCluster
from folium.plugins import HeatMap

Lectura de datos#

Registros de presencia de murciélagos

# Carga de registros de presencia de murciélagos en un dataframe de pandas

murcielagos = pd.read_csv("https://raw.githubusercontent.com/pf3311-cienciadatosgeoespaciales/2021-iii/main/contenido/b/datos/murcielagos.csv", 
                          sep="\t")

murcielagos
gbifID datasetKey occurrenceID kingdom phylum class order family genus species ... identifiedBy dateIdentified license rightsHolder recordedBy typeStatus establishmentMeans lastInterpreted mediaType issue
0 3416142999 50c9509d-22c7-4a22-a47d-8c48425ef4a7 https://www.inaturalist.org/observations/10118... Animalia Chordata Mammalia Chiroptera Vespertilionidae Rhogeessa Rhogeessa io ... Yuzefovich Alexander 2021-11-15T14:33:49 CC_BY_NC_4_0 jo22nfrog jo22nfrog NaN NaN 2021-12-08T05:34:32.772Z StillImage COORDINATE_ROUNDED
1 3416092578 50c9509d-22c7-4a22-a47d-8c48425ef4a7 https://www.inaturalist.org/observations/10131... Animalia Chordata Mammalia Chiroptera Phyllostomidae Uroderma Uroderma bilobatum ... Merav Vonshak 2021-11-17T06:37:32 CC_BY_NC_4_0 Merav Vonshak Merav Vonshak NaN NaN 2021-12-08T06:09:54.326Z StillImage COORDINATE_ROUNDED
2 3415771696 50c9509d-22c7-4a22-a47d-8c48425ef4a7 https://www.inaturalist.org/observations/88766024 Animalia Chordata Mammalia Chiroptera Phyllostomidae Artibeus Artibeus jamaicensis ... Yuzefovich Alexander 2021-07-27T06:38:03 CC_BY_NC_4_0 Alex Castelein Alex Castelein NaN NaN 2021-12-08T05:05:13.398Z StillImage COORDINATE_ROUNDED
3 3415765386 50c9509d-22c7-4a22-a47d-8c48425ef4a7 https://www.inaturalist.org/observations/36722404 Animalia Chordata Mammalia Chiroptera Phyllostomidae Uroderma Uroderma bilobatum ... Jay 2019-12-17T22:12:20 CC_BY_NC_4_0 Jay Jay NaN NaN 2021-12-08T04:59:02.428Z StillImage COORDINATE_ROUNDED
4 3415753400 50c9509d-22c7-4a22-a47d-8c48425ef4a7 https://www.inaturalist.org/observations/39440952 Animalia Chordata Mammalia Chiroptera Emballonuridae Rhynchonycteris Rhynchonycteris naso ... Jakob Fahr 2020-03-02T08:06:42 CC_BY_NC_4_0 Ben Zerante Ben Zerante NaN NaN 2021-12-08T05:48:47.800Z StillImage COORDINATE_ROUNDED
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
13184 45849434 847e2306-f762-11e1-a439-00145eb45e9a urn:catalog:LSUMZ:Mammals:12726 Animalia Chordata Mammalia Chiroptera Phyllostomidae Anoura Anoura geoffroyi ... NaN NaN CC0_1_0 NaN Gardner, Alfred L. NaN NATIVE 2021-12-02T03:54:15.435Z NaN INSTITUTION_COLLECTION_MISMATCH
13185 45849433 847e2306-f762-11e1-a439-00145eb45e9a urn:catalog:LSUMZ:Mammals:12725 Animalia Chordata Mammalia Chiroptera Phyllostomidae Anoura Anoura geoffroyi ... NaN NaN CC0_1_0 NaN Gardner, Alfred L. NaN NATIVE 2021-12-02T03:54:15.435Z NaN INSTITUTION_COLLECTION_MISMATCH
13186 45849432 847e2306-f762-11e1-a439-00145eb45e9a urn:catalog:LSUMZ:Mammals:12724 Animalia Chordata Mammalia Chiroptera Phyllostomidae Anoura Anoura geoffroyi ... NaN NaN CC0_1_0 NaN Gardner, Alfred L. NaN NATIVE 2021-12-02T03:54:15.437Z NaN INSTITUTION_COLLECTION_MISMATCH
13187 45849431 847e2306-f762-11e1-a439-00145eb45e9a urn:catalog:LSUMZ:Mammals:12723 Animalia Chordata Mammalia Chiroptera Phyllostomidae Anoura Anoura geoffroyi ... NaN NaN CC0_1_0 NaN Gardner, Alfred L. NaN NATIVE 2021-12-02T03:54:15.436Z NaN INSTITUTION_COLLECTION_MISMATCH
13188 45849415 847e2306-f762-11e1-a439-00145eb45e9a urn:catalog:LSUMZ:Mammals:10637 Animalia Chordata Mammalia Chiroptera Phyllostomidae Anoura Anoura geoffroyi ... NaN NaN CC0_1_0 NaN Arnold, Keith A. NaN NATIVE 2021-12-02T03:54:15.457Z NaN COORDINATE_REPROJECTED;INSTITUTION_COLLECTION_...

13189 rows × 50 columns

Polígonos de ASP

# Carga de registros de presencia de murciélagos en un dataframe de pandas

asp = gpd.read_file("https://github.com/pf3311-cienciadatosgeoespaciales/2021-iii/raw/main/contenido/b/datos/asp.geojson")

asp
id codigo nombre_asp cat_manejo estatus siglas_cat nombre_ac siglas_ac descripcio a_creacion n_creacion normativa area area_km version geometry
0 areas_silvestres_protegidas.1 A01 Montes Submarinos Area Marina de Manejo Estatal AMM Marina Cocos ACMC Area Marina de Manejo 2011 DE_36452 None 9.592623e+09 9.592623e+07 2019-11-19-001 POLYGON ((-87 6.13333, -86.43333 5.73333, -87....
1 areas_silvestres_protegidas.2 A02 Cabo Blanco Area Marina de Manejo Estatal AMM Area de Conservacion Tempisque ACT Area Marina de Manejo 1963 Ley_10 None 8.247806e+08 8.247806e+06 2019-11-19-001 POLYGON ((-85.14336 9.59649, -85.14332 9.59643...
2 areas_silvestres_protegidas.3 B01 Alberto Manuel Brenes Reserva Biologica Estatal RB Area de Conservacion Central ACC Area terrestre protegida 1993 Ley_7354 None 7.800440e+07 7.800440e+05 2019-11-19-001 POLYGON ((-84.63388 10.18202, -84.64167 10.177...
3 areas_silvestres_protegidas.4 B03 Isla Guayabo Reserva Biologica Estatal RB Area de Conservacion Tempisque ACT Isla 1973 DE_2858_A None 6.268092e+04 6.268092e+02 2019-11-19-001 POLYGON ((-84.8771 9.91109, -84.87737 9.91086,...
4 areas_silvestres_protegidas.5 B04 Isla Pajaros Reserva Biologica Estatal RB Area de Conservacion Pacifico Central ACOPAC Isla 1976 DE_ 5963_A None 3.211583e+04 3.211583e+02 2019-11-19-001 POLYGON ((-84.99674 10.09266, -84.99696 10.092...
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
167 areas_silvestres_protegidas.168 Z31 Quebrada Rosario Zona Protectora Estatal ZP Area de Conservacion Central ACC Area terrestre protegida 1997 DE_ 26297_MINAE None 4.292695e+05 4.292695e+03 2019-11-19-001 POLYGON ((-84.07619 9.70425, -84.07689 9.70374...
168 areas_silvestres_protegidas.169 P10 Santa Rosa Parque Nacional Estatal PN Area de Conservacion Guanacaste ACG Isla 1966 Ley_3694 None 3.230258e+06 3.230258e+04 2019-11-19-001 MULTIPOLYGON (((-85.87565 10.84304, -85.87571 ...
169 areas_silvestres_protegidas.170 V25 Bahia Junquillal Refugio Nacional de Vida Silvestre Estatal RVS Area de Conservacion Guanacaste ACG Isla 1995 DE_ 23867_MIRENEM None 1.836792e+05 1.836792e+03 2021-08-20-001 MULTIPOLYGON (((-85.7336 10.95775, -85.73329 1...
170 areas_silvestres_protegidas.171 P18 Internacional La Amistad Parque Nacional Estatal PN Area de Conservacion La Amistad Pacifico ACLAP Area terrestre protegida 1982 DE_13324_A None 1.982896e+09 1.982896e+07 2019-11-19-001 MULTIPOLYGON (((-82.85282 9.01273, -82.85414 9...
171 areas_silvestres_protegidas.172 B09 Bicentenario de la República - Pájaro Campana Reserva Biologica Estatal RB Area de Conservacion La Amistad Pacifico ACLAP Area terrestre protegida 2021 DE_42615_MINAE None 5.082743e+07 5.082743e+05 2021-08-21-001 POLYGON ((-82.77351 8.94507, -82.8105 8.95856,...

172 rows × 16 columns

Visualización preliminar#

Mapa interactivo de puntos agrupados (clusters)

# Creación del mapa base
m = folium.Map(location=[9.6, -84.2], tiles='CartoDB positron', zoom_start=8)


# Capa de ASP
folium.GeoJson(data="https://github.com/pf3311-cienciadatosgeoespaciales/2021-iii/raw/main/contenido/b/datos/asp.geojson", 
               name="ASP").add_to(m)


# Capa de registros de presencia de murciélagos agrupados
mc = MarkerCluster()
for idx, row in murcielagos.iterrows():
    if not math.isnan(row['decimalLongitude']) and not math.isnan(row['decimalLatitude']):
        mc.add_child(Marker([row['decimalLatitude'], row['decimalLongitude']], popup=row['species']))
m.add_child(mc)

# Control de capas
folium.LayerControl().add_to(m)

# Despliegue del mapa
m
Make this Notebook Trusted to load map: File -> Trust Notebook

Mapa interactivo de calor (heatmap)

# Creación del mapa base
m = folium.Map(location=[9.6, -84.2], tiles='CartoDB dark_matter', zoom_start=8)


# Capa de ASP
folium.GeoJson(data="https://github.com/pf3311-cienciadatosgeoespaciales/2021-iii/raw/main/contenido/b/datos/asp.geojson", 
               name="ASP").add_to(m)


# Capa de calor de registros de presencia de murciélagos
HeatMap(data=murcielagos[['decimalLatitude', 'decimalLongitude']], radius=10).add_to(m)

# Control de capas
folium.LayerControl().add_to(m)

# Despliegue del mapa
m
Make this Notebook Trusted to load map: File -> Trust Notebook

Cálculo de la cantidad de especies por ASP#

Conversión del dataframe de murciélagos a geodataframe

murcielagos = gpd.GeoDataFrame(murcielagos, 
                               geometry=gpd.points_from_xy(murcielagos.decimalLongitude, murcielagos.decimalLatitude))
# Join espacial de las capas de ASP y registros de presencia de murciélagos

asp_contienen_murcielagos = asp.sjoin(murcielagos, how="left", op="contains")

# Conteo de especies en cada ASP
asp_especies = asp_contienen_murcielagos.groupby("id").species.nunique()
asp_especies = asp_especies.reset_index() # para convertir la serie a dataframe

asp_especies.rename(columns = {'species': 'cantidad_especies_murcielagos'}, inplace = True)

asp_especies
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[7], line 3
      1 # Join espacial de las capas de ASP y registros de presencia de murciélagos
----> 3 asp_contienen_murcielagos = asp.sjoin(murcielagos, how="left", op="contains")
      5 # Conteo de especies en cada ASP
      6 asp_especies = asp_contienen_murcielagos.groupby("id").species.nunique()

File ~/miniconda3/envs/pf3311-2025-ii/lib/python3.13/site-packages/geopandas/geodataframe.py:2560, in GeoDataFrame.sjoin(self, df, how, predicate, lsuffix, rsuffix, **kwargs)
   2468 def sjoin(
   2469     self,
   2470     df: GeoDataFrame,
   (...)   2475     **kwargs,
   2476 ) -> GeoDataFrame:
   2477     """Spatial join of two GeoDataFrames.
   2478 
   2479     See the User Guide page :doc:`../../user_guide/mergingdata` for details.
   (...)   2558     sjoin : equivalent top-level function
   2559     """
-> 2560     return geopandas.sjoin(
   2561         left_df=self,
   2562         right_df=df,
   2563         how=how,
   2564         predicate=predicate,
   2565         lsuffix=lsuffix,
   2566         rsuffix=rsuffix,
   2567         **kwargs,
   2568     )

File ~/miniconda3/envs/pf3311-2025-ii/lib/python3.13/site-packages/geopandas/tools/sjoin.py:109, in sjoin(left_df, right_df, how, predicate, lsuffix, rsuffix, distance, on_attribute, **kwargs)
    107 if kwargs:
    108     first = next(iter(kwargs.keys()))
--> 109     raise TypeError(f"sjoin() got an unexpected keyword argument '{first}'")
    111 on_attribute = _maybe_make_list(on_attribute)
    113 _basic_checks(left_df, right_df, how, lsuffix, rsuffix, on_attribute=on_attribute)

TypeError: sjoin() got an unexpected keyword argument 'op'

Salidas#

Tabular#

# Join para agregar la columna con el conteo a la capa de ASP
asp_especies = asp_especies.join(asp.set_index('id'), on='id', rsuffix='_b')

asp_especies[["nombre_asp", "cantidad_especies_murcielagos"]].sort_values("cantidad_especies_murcielagos", ascending=[False])
nombre_asp cantidad_especies_murcielagos
51 Golfo Dulce 54
25 Arenal Monteverde 31
111 Palo Verde 29
5 La Selva 27
129 Barra del Colorado 27
... ... ...
56 Humedal Laguna Madrigal 0
55 Rio Oro 0
54 Preciosa Platanares 0
53 Punta Rio Claro 0
171 Montana de El Tigre 0

172 rows × 2 columns

Gráfica#

Se presentan salidas de gráficos estadísticos en Matplotlib y plotly.

# Dataframe filtrado para usar en graficación
asp_especies_grafico = asp_especies[["nombre_asp", "cantidad_especies_murcielagos"]].sort_values("cantidad_especies_murcielagos", ascending=[False]).head(15)

Matplotlib

# Graficación
asp_especies_grafico.plot(x='nombre_asp', 
                          y='cantidad_especies_murcielagos', 
                          kind='bar', 
                          figsize=(20,10))

# Título y leyendas en los ejes
plt.title('Cantidad de especies de murciélagos por ASP', fontsize=20)
plt.xlabel('ASP', fontsize=16)
plt.ylabel('Cantidad de especies', fontsize=16)
Text(0, 0.5, 'Cantidad de especies')
../../_images/8750e0fcb2d64ff1f2fbc64e38795c20e669837cefb20db165656b90f7b4c34f.png

plotly

fig = px.bar(asp_especies_grafico, 
             x='nombre_asp', 
             y='cantidad_especies_murcielagos', 
             labels={'nombre_asp':'ASP', 'cantidad_especies_murcielagos':'Cantidad de especies'},
             title="Cantidad de especies de murciélagos por ASP")

fig.show()

Para más información sobre plotly, se recomienda ver Data Visualization as The First and Last Mile of Data Science Plotly Express and Dash | SciPy 2021.

Geoespacial#

# Creación del mapa base
m = folium.Map(location=[9.8, -84], tiles='CartoDB positron', zoom_start=8)

folium.Choropleth(
    name="Cantidad de especies en ASP",
    geo_data=asp,
    data=asp_especies,
    columns=['id', 'cantidad_especies_murcielagos'],
    bins=8,
    key_on='feature.properties.id',
    fill_color='Reds', 
    fill_opacity=0.5, 
    line_opacity=1,
    legend_name='Cantidad de especies de murciélagos',
    smooth_factor=0).add_to(m)

# Control de capas
folium.LayerControl().add_to(m)

# Despliegue del mapa
m
Make this Notebook Trusted to load map: File -> Trust Notebook